﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace BOIUK.WindowsFormsControls
{    /// <summary>
    /// Struct to handle the caret (L, C) co-ordinates
    /// L = one-based line number of the line containing the caret
    /// C = one-based column number in the line containing the caret
    /// </summary>
    public struct CharPoint
    {
        private int l, c;

        public static readonly CharPoint Empty;

        static CharPoint()
        {
            CharPoint.Empty = new CharPoint();
        }

        public int Line
        {
            get { return l; }
        }

        public int Column
        {
            get { return c; }
        }


        public CharPoint(int l, int c)
        {
            this.l = l;
            this.c = c;
        }

        public override string ToString()
        {
            return (String.Format("{{L={0}, C={1}}}", this.l, this.c));
        }
    }

    public class RichTextBoxExtended : RichTextBox
    {
        #region Caret Position

        [DllImport("user32")]
        private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
        [DllImport("user32")]
        private static extern int GetCaretPos(ref Point lpPoint);

        private int EM_LINEINDEX = 0xbb;
        private int EM_LINEFROMCHAR = 0xc9;
        private int EM_GETSEL = 0xb0;

        /// <summary>
        /// Gets the caret current (X, Y) position.
        /// </summary>
        /// <value>Point struct</value>
        public Point GetCaretXYPosition
        {
            get
            {
                Point pt = Point.Empty;
                // get a Point struct with the caret current (X, Y) position
                GetCaretPos(ref pt);
                // return the Point struct with the caret current (X, Y) position
                return pt;
            }
        }

        /// <summary>
        /// Gets the caret current (L, C) position.
        /// </summary>
        /// <value>CharPoint struct</value>
        public CharPoint GetCaretLCPosition
        {
            get
            {
                CharPoint cpt = CharPoint.Empty;

                // save the handle reference for the ExtToolBox
                HandleRef hr = new HandleRef(this, base.Handle);

                // Send the EM_LINEFROMCHAR message with the value of -1 in wParam.
                // The return value is the zero-based line number of the line containing the caret.
                int l = (int) SendMessage(hr, EM_LINEFROMCHAR, -1, 0);

                // Send the EM_GETSEL message to the ToolBox control.
                // The low-order word of the return value is the character position of the caret relative to the
                // first character in the ToolBox control, i.e. the absolute character index.
                int sel = (int) SendMessage(hr, EM_GETSEL, 0, 0);

                // get the low-order word from sel
                int ai = sel & 0xffff;

                // Send the EM_LINEINDEX message with the value of -1 in wParam.
                // The return value is the number of characters that
                // precede the first character in the line containing the caret.
                int li = (int) SendMessage(hr, EM_LINEINDEX, -1, 0);

                // Subtract the li (line index) from the ai (absolute character index),
                // The result is the column number of the caret position in the line containing the caret.
                int c = ai - li;

                // Add 1 to the l and c since these are zero-based.
                cpt = new CharPoint(l + 1, c + 1);

                // Return a CharPoint with the caret current (L,C) position
                return cpt;
            }
        }
        #endregion

        #region Column Guides

        /// <summary>
        /// Guesstimated SelectionMargin Width
        /// </summary>
        private const int MARGIN_WIDTH = 9;

        private const int WM_PAINT = 0x000f;
        private const int WM_HSCROLL = 0x0114;

        public enum ScrollBarCommands
        {
            SB_LINEUP = 0,
            SB_LINELEFT = 0,
            SB_LINEDOWN = 1,
            SB_LINERIGHT = 1,
            SB_PAGEUP = 2,
            SB_PAGELEFT = 2,
            SB_PAGEDOWN = 3,
            SB_PAGERIGHT = 3,
            SB_THUMBPOSITION = 4,
            SB_THUMBTRACK = 5,
            SB_TOP = 6,
            SB_LEFT = 6,
            SB_BOTTOM = 7,
            SB_RIGHT = 7,
            SB_ENDSCROLL = 8
        }

        /// <summary>
        /// Are we scrolling or not?
        /// </summary>
        bool hScroll = false;

        #region Properties

        [Category("Extended Appearance")]
        [Description("The color of the selection margin")]
        public Color SelectionMarginColor
        {
            get { return _selectionMarginColor; }
            set
            {
                _selectionMarginColor = value;
                RePaint();
            }
        }
        private Color _selectionMarginColor = Color.BlanchedAlmond;

        [Category("Extended Appearance")]
        [Description("The color of the column guide lines")]
        public Color ColumnGuideLineColor
        {
            get { return _columnGuideLineColor; }
            set
            {
                _columnGuideLineColor = value;
                RePaint();
            }
        }
        private Color _columnGuideLineColor = Color.LightGray;

        [Category("Extended Appearance")]
        [Description("Show column guide lines")]
        public bool ShowColumnGuides
        {
            get { return _showColumnGuides; }
            set
            {
                _showColumnGuides = value;
                this.Invalidate();
                RePaint();
            }
        }
        private bool _showColumnGuides = false;

        #endregion

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_HSCROLL)
            {
                if (m.WParam == (IntPtr) ScrollBarCommands.SB_ENDSCROLL)
                {
                    //user has stop scrolling
                    hScroll = false;
                    //force a full repaint (removing any previous lines)
                    this.Invalidate();
                }
                else
                    //set hScroll true, to prevent OnPaint drawing
                    hScroll = true;
            }

            //we've been told to paint
            if (m.Msg == WM_PAINT)
            {
                //allow the base textbox to process the message
                base.WndProc(ref m);
                //then force our paint
                RePaint();
            }

            //process all other messages
            base.WndProc(ref m);
        }

        /// <summary>
        /// Call the OnPaint event
        /// </summary>
        private void RePaint()
        {
            using (Graphics graphics = base.CreateGraphics())
            {
                OnPaint(new PaintEventArgs(graphics, base.ClientRectangle));
            }
        }

        protected override void OnTextChanged(EventArgs e)
        {
            //force a repaint
            RePaint();

            base.OnTextChanged(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            //let the base TextBox paint (text, border etc)
            base.OnPaint(e);

            if (!hScroll)
            {
                //we're not scrolling horizontally
                using (Graphics dc = e.Graphics)
                {
                    //draw lines every 10 chars
                    Size fontSize = TextRenderer.MeasureText("123456789", this.Font);
                    //SizeF fontSize = dc.MeasureString("12345678", this.Font);

                    int i = fontSize.Width;

                    if (this.ShowSelectionMargin)
                    {
                        //accommodate for the selection margin
                        i += MARGIN_WIDTH;

                        //colour in the margin
                        using (SolidBrush marginBrush = new SolidBrush(SelectionMarginColor))
                        {
                            Rectangle marginRect = new Rectangle(0, 0, MARGIN_WIDTH, this.ClientRectangle.Height);
                            dc.FillRectangle(marginBrush, marginRect);
                        }
                    }

                    if (this.ShowColumnGuides)
                        using (Pen pen = new Pen(ColumnGuideLineColor, 1f))
                        {
                            do
                            {
                                dc.DrawLine(pen, new Point(i, 0), new Point(i, this.ClientRectangle.Height));
                                i += fontSize.Width;

                            } while (i < this.ClientRectangle.Width);
                        }
                }
            }
        }

        #endregion
    }
}
